#include "parseHttpMessage.h"
#include "server.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>

#define HOMEPAHE "index.html"
#define HTTPVERSHION "HTTP/1.1"
#define WEBROOT "wwwroot"

static std::string code2Description(size_t code)
{
    switch(code)
    {
        case 200:
            return "ok";
        case 400:
            return "not found";
        default:
            return "not found";
    }
}

static std::string suffix2Desc(const std::string& suffix)
{
    if(strcasecmp(suffix.c_str(), "html") == 0)
  {
    return "text/html";
  }
  else if(strcasecmp(suffix.c_str(), "css") == 0)
  {
    return "text/css";
  }
  else if(strcasecmp(suffix.c_str(), "js") == 0)
  {
    return "application/javascript";
  }
  else if(strcasecmp(suffix.c_str(), "jpg") == 0)
  {
    return "image/jpeg";
  }
  else if(strcasecmp(suffix.c_str(), "png") == 0)
  {
    return "image/png";
  }
  else if(strcasecmp(suffix.c_str(), "gif") == 0)
  {
    return "image/gif";
  }
  else if(strcasecmp(suffix.c_str(), "ico") == 0)
  {
    return "image/x-icon";
  }
  else if(strcasecmp(suffix.c_str(), "exe") == 0)
  {
    return "application/json";
  }
  else 
  {
    return "text/html";
  }
}

HttpRequest::HttpRequest()
:req_blank_("\r\n")
,content_length_(0)
,path_(WEBROOT)
,iscgi_(false)
,filesize_(0)
{

}

HttpRequest::~HttpRequest()
{

}

const std::string& HttpRequest::getParam()
{
    return param_;
}

const std::string& HttpRequest::getMethod()
{
    return method_;
}

const std::string& HttpRequest::getBody()
{
    return req_body_;
}

bool HttpRequest::isLegalMethod()
{
    return isGetMethod() || isPostMethod();
}

bool HttpRequest::isGetMethod()
{
    return strcasecmp(method_.c_str(), "GET") == 0;
}

bool HttpRequest::isPostMethod()
{
    return strcasecmp(method_.c_str(), "POST") == 0;
}

void HttpRequest::parseUrl()
{
    size_t pos = url_.rfind('?');
    if(pos != std::string::npos)
    {
        path_ += url_.substr(0, pos);
        param_ = url_.substr(pos+1);
        iscgi_ = true;
    }
    else 
    {
        path_ += url_;
    }
}

void HttpRequest::setUrlToPath()
{
    path_ += url_;
}

void HttpRequest::addHomePage()
{
    if(path_[path_.size() - 1] == '/')
    {
        path_ += HOMEPAHE;
    }
}

void HttpRequest::setCgi()
{
    iscgi_ = true;
}

bool HttpRequest::getCgi()
{
    return iscgi_;
}

void HttpRequest::setFileSize(size_t filesize)
{
    filesize_ = filesize;
}

size_t HttpRequest::getFileSize()
{
    return filesize_;
}

std::string HttpRequest::getPathSuffix()
{
    size_t pos = path_.rfind('.');
    if(pos != std::string::npos)
    {
        return path_.substr(pos + 1);
    }
    return "";
}

void HttpRequest::setPath(const std::string& path)
{
    path_ = path;
}

std::string HttpRequest::getPath()
{
    return path_;
}

void HttpRequest::parseMsg(const char* msg, size_t len)
{
    std::string msg_str = msg;
    //获取请求行
    size_t beginpos = 0;
    size_t endpos = 0;
    size_t pos = msg_str.find('\n');
    if(pos != std::string::npos)
    {
        endpos = pos;
        // \r\n
        if(msg_str[pos-1] == '\r')
        {
            endpos--;
        }
        req_line_ = msg_str.substr(beginpos, endpos - beginpos);
        beginpos = pos + 1;

        //LOG_INFO("FFL[%s,%s,%d] reqline:%s", LOG_FFL, req_line_.c_str());
    }
    //解析请求行
    if(!req_line_.empty())
    {
        size_t line_beginpos = 0;
        size_t line_endpos = 0;
        size_t line_pos = req_line_.find(' ');
        if(line_pos != std::string::npos)
        {
            line_endpos = line_pos;
            method_ = req_line_.substr(line_beginpos, line_endpos - line_beginpos);
            line_beginpos = line_pos + 1;

            //LOG_INFO("FFL[%s,%s,%d] method:%s", LOG_FFL, method_.c_str());
        }

        line_pos = req_line_.find(' ', line_beginpos);
        if(line_pos != std::string::npos)
        {
            line_endpos = line_pos;
            url_ = req_line_.substr(line_beginpos, line_endpos - line_beginpos);
            line_beginpos = line_pos + 1;

            //LOG_INFO("FFL[%s,%s,%d] url:%s", LOG_FFL, url_.c_str());

        }
        version_ = req_line_.substr(line_beginpos);
        //LOG_INFO("FFL[%s,%s,%d] version:%s", LOG_FFL, version_.c_str());
    }

    //获取报头
    std::string line;
    do
    {
        line.clear();

        pos = msg_str.find('\n', beginpos);
        if(pos != std::string::npos)
        {
            endpos = pos;
            if(msg_str[pos - 1] == '\r')
            {
                endpos = pos - 1;
            }

            line = msg_str.substr(beginpos, endpos - beginpos);

            //LOG_INFO("FFL[%s,%s,%d] head_line:%s", LOG_FFL, line.c_str());
            req_header_.push_back(line);

            //加到map
            size_t line_pos = line.find(':');
            if(line_pos != std::string::npos)
            {
                std::string key = line.substr(0, line_pos);
                std::string val = line.substr(line_pos + 2);
                header_map_[key] = val;
                //LOG_INFO("FFL[%s,%s,%d] head_key:%s, head_val:%s", LOG_FFL, key.c_str(), val.c_str());
                if(strcmp(key.c_str(), "Content_Length") == 0)
                {
                    content_length_ = atoi(val.c_str());
                }
            }
            beginpos = pos + 1;
        }

    }while(!line.empty());

    req_body_ = msg_str.substr(beginpos, content_length_);
    //LOG_INFO("FFL[%s,%s,%d] req_body:%s", LOG_FFL, req_body_.c_str());
}

HttpResponse::HttpResponse()
:resp_blank_("\r\n")
{

}

HttpResponse::~HttpResponse()
{
    if(resp_body_)
    {
        std::cout << "~HttpResponse" << std::endl;
        free(resp_body_);
        resp_body_ = nullptr;
    }
}

void HttpResponse::makeRespLine(size_t code)
{
    resp_line_ += HTTPVERSHION;
    resp_line_ += ' ';
    resp_line_ += std::to_string(code);
    resp_line_ += ' ';
    resp_line_ += code2Description(code);
    resp_line_ += "\r\n";

    //LOG_INFO("FFL[%s, %s, %d] respline:%s", LOG_FFL, resp_line_.c_str());
}

void HttpResponse::addHeader(const std::string& header)
{
    resp_header_.push_back(header);
}

// void HttpResponse::addBody(const char* body)
// {
//     resp_body_ += body;
//     LOG_INFO("BODY SIZE:%d", resp_body_.size());
// }

void HttpResponse::sendResponse(connection* conn)
{
    conn->addReply(resp_line_.c_str(), resp_line_.size());

    for(int i = 0; i < resp_header_.size(); ++i)
    {
        conn->addReply(resp_header_[i].c_str(), resp_header_[i].size());
    }

    conn->addReply(resp_blank_.c_str(), resp_blank_.size());
    if(resp_body_)
    {
        conn->addReply(resp_body_->buf, resp_body_->size_);
        free(resp_body_);
        resp_body_ = nullptr;
    }

}

HttpEndPoint::HttpEndPoint(connection* conn)
{
    conndata_ = conn;
}

HttpEndPoint::~HttpEndPoint()
{
    conndata_ = nullptr;
}

void HttpEndPoint::setConnData(connection* connptr)
{
    conndata_ = connptr;
}

void HttpEndPoint::parseRequest(const char* msg, size_t msglen)
{
    req_.parseMsg(msg, msglen);
}

void HttpEndPoint::makeResponse()
{
    //解析请求
    size_t code = 200;
    std::string path_tmp;
    if(!req_.isLegalMethod())
    {
        code = 400;
        goto END;
    }

    if(req_.isGetMethod())
    {
        //解析url，可能会有参数
        req_.parseUrl();
    }
    else if(req_.isPostMethod())
    {
        //参数在正文
        req_.setUrlToPath();
    }

    req_.addHomePage();

    //判断是否是目录，是目录加HOMEPAGE
    path_tmp = req_.getPath();
    struct stat st;
    if(stat(path_tmp.c_str(), &st) < 0)
    {
        code = 400;
        goto END;
    }

    //
    if(S_ISDIR(st.st_mode))
    {
        path_tmp += "/";
        req_.setPath(path_tmp);
        req_.addHomePage();
    }
    else
    {
        //看是否是可执行文件
        if(st.st_mode & S_IXUSR || st.st_mode & S_IXGRP || st.st_mode & S_IXOTH)
        {
            req_.setCgi();
        }
        else
        {
            //普通网页
        }
    }

    if(!req_.getCgi())
    {
      req_.setFileSize(st.st_size);
      //Logger::info("-----filesize:%d", st.st_size);
    }

END:
    makeRespLine(code);
    makeRespHeader();
    makeRespBody();
}

void HttpEndPoint::makeRespLine(size_t code)
{
    resp_.makeRespLine(code);
}

void HttpEndPoint::makeRespHeader()
{
    std::string suffix = req_.getPathSuffix();
    std::string content_type = "Content_Type: ";
    content_type += suffix2Desc(suffix);
    content_type += "\r\n";
    resp_.addHeader(content_type);

    // if(!req_.getCgi())
    // {
    //     std::string content_length = "Content_Length: ";
    //     content_length += std::to_string(req_.getFileSize());
    //     content_length += "\r\n";
    //     resp_.addHeader(content_length);
    // }

    //LOG_INFO("FFL[%s, %s, %d], content_type:%s", LOG_FFL, content_type.c_str());
}

void HttpEndPoint::exeCgi()
{
    //cgi，访问可执行程序
    //创建进程，子进程进程替换
    //参数可以使用环境变量和管道方式传递

    std::string method_env = "METHOD_ENV=";
    method_env += req_.getMethod();
    putenv((char*)method_env.c_str());

    //创建管道，in和out是相对于子进程的，索引0读，1是写
    int fd_in[2] = {0};
    int fd_out[2] = {0};
    pipe(fd_in);
    pipe(fd_out);

    pid_t pid = fork();
    if(pid == 0)
    {
        //子进程
        close(fd_in[1]);
        close(fd_out[0]);

        //Get方法参数因为是环境变量传递，不是从文件中读取
        //读取时不会阻塞，需要提前传递参数
        if(req_.isGetMethod())
        {
            std::string param = "PARAM_ENV=";
            param += req_.getParam();
            putenv((char*)param.c_str());
        }

        //重定向
        dup2(0, fd_in[0]);
        dup2(1, fd_out[1]);

        std::string path = req_.getPath();
        execl(path.c_str(), path.c_str(), nullptr);

    }
    else if(pid > 0)
    {
        //父进程
        close(fd_in[0]);
        close(fd_out[1]);

        if(req_.isPostMethod())
        {
            //通过管道传递post的body
            const std::string& reqbody = req_.getBody();
            //write(fd_in[1], reqbody.c_str(), reqbody.size());
            for(int i = 0; i < reqbody.size(); ++i)
            {
                write(fd_in[1], &reqbody[i], 1);
            }
        }

        //读取子进程处理返回值
        size_t s = 0;
        std::string content;
        do
        {
            char c = 0;
            s = read(fd_out[0], &c, 1);
            if(s <= 0)
                break;
            content += c;
            /* code */
        } while (s > 0);
        

        waitpid(pid, nullptr, 0);
        close(fd_in[1]);
        close(fd_out[0]);

    }
    else
    {
        close(fd_in[0]);
        close(fd_in[1]);
        close(fd_out[0]);
        close(fd_out[1]);
    }
}

void HttpEndPoint::exeNoCgi()
{
    std::string path = req_.getPath();
    int fd = open(path.c_str(), O_RDONLY);
    if(fd < 0)
    {
        LOG_ERROR("FFL[%s, %s, %d] open %s fail", LOG_FFL, path.c_str());
        return; 
    }
    LOG_INFO("FFL[%s, %s, %d] open %s success", LOG_FFL, path.c_str());

    size_t filesize = req_.getFileSize();
    //char* buf = new char[filesize];
    //void* ptr = (char*)operator new(sizeof(HttpResponse::RespBodyType) + filesize * sizeof(char));
    resp_.resp_body_ = (HttpResponse::RespBodyType*)malloc(sizeof(HttpResponse::RespBodyType) + filesize * sizeof(char));
    if(!resp_.resp_body_)
    {
        LOG_ERROR("FFL[%s, %s, %d] read fail filesize:%s", LOG_FFL, filesize);
        close(fd);
        return;
    }

    resp_.resp_body_->size_ = filesize;
    int nread = read(fd, resp_.resp_body_->buf, filesize + 5);
    if(nread < 0)
    {
        LOG_ERROR("FFL[%s, %s, %d] read fail nread:%s", LOG_FFL, nread);
        close(fd);
        free(resp_.resp_body_);
        resp_.resp_body_ = nullptr;
        return;
    }

    if(nread != filesize)
    {
        LOG_ERROR("FFL[%s, %s, %d] read uncomplete nread:%d, filesize:%d", LOG_FFL, nread, filesize);
        close(fd);
        free(resp_.resp_body_);
        resp_.resp_body_ = nullptr;
        return;
    }

    //resp_.addBody(resp_.resp_body_);
   
    //delete buf;
    close(fd);
}

void HttpEndPoint::makeRespBody()
{
    if(req_.getCgi())
    {
        exeCgi();
    }
    else 
    {
        exeNoCgi();
    }
}

void HttpEndPoint::sendResponse()
{
    if(conndata_ == nullptr)
        return; 
    
    connection* conn = (connection*)conndata_;

    resp_.sendResponse(conn);
}